package runnjump;

import control.GameEngine;
import data.PlayerEntity;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.util.glu.GLU.*;



import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import org.lwjgl.opengl.PixelFormat;

/**
 * @author JtR
 */
public class Main {

    public static GameEngine engine;
    public static final int DISPLAY_HEIGHT = 480;
    public static final int DISPLAY_WIDTH = 640;
    private static int finalScreenWidth = 320;
    private static int finalScreenHeight = 240;
    private static int occquery;
    private static final IntBuffer samples = BufferUtils.createIntBuffer(1);
    private static boolean upPressed = false;
    private static boolean downPressed = false;
    private static boolean leftPressed = false;
    private static boolean rightPressed = false;
    private static long lastFrameTime;

    public static void main(String[] args) {
        Main main = null;
        try {
            main = new Main();
            main.create();
            main.run();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (main != null) {
                main.destroy();
            }
        }
    }

    public Main() {
    }

    public void create() throws LWJGLException {
        //Display
        Display.setDisplayMode(new DisplayMode(DISPLAY_WIDTH, DISPLAY_HEIGHT));
        Display.setFullscreen(false);
        Display.setTitle("Test Game!");
        Display.create(new PixelFormat(8, 8, 8));

        //Keyboard
        Keyboard.create();

        //Mouse
        Mouse.setGrabbed(false);
        Mouse.create();

        //OpenGL
        initGL();
        resizeGL();
    }

    public void destroy() {
        //Methods already check if created before destroying.
        Mouse.destroy();
        Keyboard.destroy();
        Display.destroy();
        //engine.destroy();
    }

    public void initGL() {
        engine = new GameEngine();

        glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_LIGHTING);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        // Query setup.
        IntBuffer queries = BufferUtils.createIntBuffer(1);
        glGenQueries(queries);
        occquery = queries.get(0);
    }

    public void processKeyboard() {
        while (Keyboard.next()) {
            if (Keyboard.getEventKeyState()) {
                // Button down
                switch (Keyboard.getEventKey()) {
                    case Keyboard.KEY_W:
                        upPressed = true;
                        break;

                    case Keyboard.KEY_A:
                        leftPressed = true;
                        break;

                    case Keyboard.KEY_S:
                        downPressed = true;
                        break;

                    case Keyboard.KEY_D:
                        rightPressed = true;
                        break;
                }
            } else {
                // Button up
                switch (Keyboard.getEventKey()) {
                    case Keyboard.KEY_W:
                        upPressed = false;
                        break;

                    case Keyboard.KEY_A:
                        leftPressed = false;
                        break;

                    case Keyboard.KEY_S:
                        downPressed = false;
                        break;

                    case Keyboard.KEY_D:
                        rightPressed = false;
                        break;
                }
            }
        }
    }

    public void processMouse() {
        // Nothing yet
    }

    private void renderLevel() {
        glEnable(GL_TEXTURE_2D);
        glColor3f(1.0f, 1.0f, 1.0f);
        engine.getCurrentLevel().getLevelImage().bind();
        glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 1.0f);
        glVertex2f(0.0f, 0.0f);
        glTexCoord2f(1.0f, 1.0f);
        glVertex2f(engine.getCurrentLevel().getWidth(), 0.0f);
        glTexCoord2f(1.0f, 0.0f);
        glVertex2f(engine.getCurrentLevel().getWidth(), engine.getCurrentLevel().getHeight());
        glTexCoord2f(0.0f, 0.0f);
        glVertex2f(0.0f, engine.getCurrentLevel().getHeight());
        glEnd();
        glDisable(GL_TEXTURE_2D);
    }

    private void renderPlayer() {
        renderPlayer(engine.getPlayer().getX(), engine.getPlayer().getY(), false);
    }

    private void renderPlayer(int x, int y, boolean collisionTest) {
        PlayerEntity pl = engine.getPlayer();

        int texIndex = collisionTest ? 0 : pl.getAnimationInfos().currentTexture;
        pl.getTextures().getSprite(0, 0).bind();

        float w2 = pl.getWidth() / 2;
        float h2 = pl.getHeight() / 2;

        glPushMatrix();
        /*glTranslated(pl.getX(), pl.getY(), 0);
        glRotatef(pl.rotationAngle, 0.0f, 0.0f, 1.0f);
        glTranslated(-pl.getX(), -pl.getY(), 0);*/
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 0.0f);
            glVertex2f(x - w2, y - h2);
            glTexCoord2f(1.0f, 0.0f);
            glVertex2f(x + w2, y - h2);
            glTexCoord2f(1.0f, 1.0f);
            glVertex2f(x + w2, y + h2);
            glTexCoord2f(0.0f, 1.0f);
            glVertex2f(x - w2, y + h2);
        glEnd();

        glPopMatrix();
    }

    /**
     * Change projection matrix, so that the right part of the level ist shown
     * with the player nearest to the center as possible
     */
    private void makeProjection() {
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();


        int left, right, bottom, top;



        if (engine.getPlayer().getX() < finalScreenWidth / 2) {  // left border
            left = 0;
            right = finalScreenWidth;


        } else if (engine.getPlayer().getX() > engine.getCurrentLevel().getWidth() - (finalScreenWidth / 2)) {  // right border
            left = engine.getCurrentLevel().getWidth() - finalScreenWidth;
            right = engine.getCurrentLevel().getWidth();


        } else {
            left = engine.getPlayer().getX() - (finalScreenWidth / 2);
            right = engine.getPlayer().getX() + (finalScreenWidth / 2);


        }
        if (engine.getPlayer().getY() < finalScreenHeight / 2) {  // bottom border
            bottom = 0;
            top = finalScreenHeight;


        } else if (engine.getPlayer().getY() > engine.getCurrentLevel().getHeight() - (finalScreenHeight / 2)) {  // top border
            bottom = engine.getCurrentLevel().getHeight() - finalScreenHeight;
            top = engine.getCurrentLevel().getHeight();


        } else {
            bottom = engine.getPlayer().getY() - (finalScreenHeight / 2);
            top = engine.getPlayer().getY() + (finalScreenHeight / 2);


        }

        gluOrtho2D(left, right, bottom, top);


    }

    public void render() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        makeProjection();

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        renderLevel();
        renderPlayer();
    }

    public void resizeGL() {
        //2D Scene
        glViewport(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);


    }

    public void run() {
        long currentTime = System.currentTimeMillis();
        lastFrameTime = System.currentTimeMillis();
        while (!Display.isCloseRequested() && !Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
            if (Display.isVisible()) {
                processKeyboard();
                processMouse();
                currentTime = System.currentTimeMillis();
                update((int)(currentTime - lastFrameTime));
                lastFrameTime = currentTime;
                render();


            } else {
                if (Display.isDirty()) {
                    render();


                }
                try {
                    Thread.sleep(100);


                } catch (InterruptedException ex) {
                }
            }
            Display.update();
            Display.sync(60);


        }
    }

    private boolean playerOnGround() {
        return playerOnGround(engine.getPlayer().getX(), engine.getPlayer().getY());
    }

    private boolean playerOnGround(int x, int y) {
        return collision(x, y - 1);
    }

    public void update(int timeDiff) {
        final int UNIT = 2;
        // Change positions relative to current key-presses into a temp var
        // Gravity
        if(playerOnGround() || timeDiff == 0) {
            engine.getPlayer().getDirection().y = 0.0f;
        } else {
            double fallHeight = (10 * UNIT) * (timeDiff / 1000.0);
            engine.getPlayer().getDirection().y -= fallHeight;
        }
        
        if (upPressed && !downPressed) {
            // UP
            if(playerOnGround()) {
                // Jump
                engine.getPlayer().getDirection().y = 8.0f;

                upPressed = false;
            }
        }
        if (leftPressed && !rightPressed) {
            // LEFT
            engine.getPlayer().rotationAngle += 4;
            engine.getPlayer().getDirection().x = -1.0f;
        } else if (rightPressed && !leftPressed) {
            // RIGHT
            engine.getPlayer().rotationAngle -= 4;
            engine.getPlayer().getDirection().x = 1.0f;
        } else {
            engine.getPlayer().getDirection().x = 0.0f;
        }
        engine.getPlayer().rotationAngle %= 360;
        
        int playerXTemp = engine.getPlayer().getX() + (int)engine.getPlayer().getDirection().x;
        int playerYTemp = engine.getPlayer().getY() + (int)engine.getPlayer().getDirection().y;

        
        // Check for collision
        if(!collision(playerXTemp, playerYTemp)) {
            engine.getPlayer().setX(playerXTemp);
            engine.getPlayer().setY(playerYTemp);
            return;
        }


        final float STEP_SIZE = 1f;
        if(engine.getPlayer().getDirection().y > 0) {
            if(collision(playerXTemp - 1, playerYTemp) && collision(playerXTemp + 1, playerYTemp)) {
                
                // Bumped into ceiling
                if(collision(playerXTemp, engine.getPlayer().getY())) {
                    engine.getPlayer().getDirection().y = 0;
                    return;
                }

                for(int tempY = playerYTemp; tempY >= engine.getPlayer().getY(); tempY -= STEP_SIZE) {
                    if(!collision(playerXTemp, (int)tempY)) {
                        engine.getPlayer().getDirection().y = 0;
                        engine.getPlayer().setX(playerXTemp);
                        engine.getPlayer().setY(tempY);
                        return;
                    }
                }


                if(collision(engine.getPlayer().getX(), engine.getPlayer().getY())) {
                    throw new RuntimeException("Fehler bei alter Position!");
                }
                /*
                if(collision(playerXTemp, engine.getPlayer().getY())) {
                    throw new RuntimeException("Kollision in Horizontaler!");
                }
                throw new RuntimeException("Darf nicht passieren!");
                 * 
                 */
            }
        } else {



        // Vary position and check again for collision
        // Varying in upward direction for an eventual diagonal floor
        // leading upward
        int MAX_Y_JUMP = 2;
        for(int i = 0; i < MAX_Y_JUMP; i++) {
            playerYTemp++;
            if(!collision(playerXTemp, playerYTemp)) {
                engine.getPlayer().setX(playerXTemp);
                engine.getPlayer().setY(playerYTemp);
                //System.out.println("Jump Fix");
                return;
            }
        }


        // Check if player is falling to fast
        if(engine.getPlayer().getDirection().y < 0) {
            for(int tempY = playerYTemp; tempY <= engine.getPlayer().getY(); tempY += STEP_SIZE) {
                if(!collision(playerXTemp, tempY)) {
                    engine.getPlayer().setX(playerXTemp);
                    engine.getPlayer().setY(tempY);
                    engine.getPlayer().getDirection().y = 0;
                    //System.out.println("Fast falling fix");
                    return;
                }
            }
            if(collision(engine.getPlayer().getX(), engine.getPlayer().getY())) {
                    throw new RuntimeException("Fehler bei alter Position!");
            }
            /*
            if(collision(playerXTemp, engine.getPlayer().getY())) {
                throw new RuntimeException("Kollision in Horizontaler!");
            }
            throw new RuntimeException("Darf nicht passieren!");
            */
        }
        }
        
        playerXTemp = engine.getPlayer().getX();
        playerYTemp = engine.getPlayer().getY();

        // Change position or not if collision
        engine.getPlayer().setX(playerXTemp);
        engine.getPlayer().setY(playerYTemp);
        //System.out.println("Old Position fix");
    }

    public boolean collision(int playerX, int playerY) {
        glEnable(GL_ALPHA_TEST);
        glAlphaFunc(GL_GEQUAL, 0.5f);
        //glEnable(GL_SCISSOR_TEST);
        glEnable(GL_STENCIL_TEST);
        glColorMask(false, false, false, false);

        //glScissor(minLeft + Game.SCREEN_WIDTH_DIV_2, minBottom + Game.SCREEN_HEIGHT_DIV_2, Math.abs(maxRight - minLeft), Math.abs(maxTop - minBottom));


        glClear(GL_STENCIL_BUFFER_BIT);

        glStencilFunc(GL_ALWAYS, 1, 1);
        glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

        renderPlayer(playerX, playerY, true);

        glStencilFunc(GL_EQUAL, 1, 1);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

        glBeginQuery(GL_SAMPLES_PASSED, occquery);

        renderLevel();

        glEndQuery(GL_SAMPLES_PASSED);

        do {
            glGetQueryObject(occquery, GL_QUERY_RESULT_AVAILABLE, samples);
        } while (samples.get(0) == 0);

        glGetQueryObject(occquery, GL_QUERY_RESULT, samples);

        glColorMask(true, true, true, true);
        glDisable(GL_STENCIL_TEST);
        //glDisable(GL_SCISSOR_TEST);
        glDisable(GL_ALPHA_TEST);

        return samples.get(0) > 0;
    }
}
